package edu.washington.cs.workflow.recommender;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import edu.washington.cs.workflow.analysis.guistructure.EventEntity;
import edu.washington.cs.workflow.analysis.guistructure.UIAbstractEntity;
import edu.washington.cs.workflow.analysis.guistructure.UIActionEntity;
import edu.washington.cs.workflow.analysis.guistructure.UIActionHandler;
import edu.washington.cs.workflow.analysis.guistructure.UIActionTracer;
import edu.washington.cs.workflow.analysis.guistructure.UIMethodEntity;
import edu.washington.cs.workflow.util.Utils;

/**
 * It analyzes the given trace and produce the following results:
 *   - a list of action being invoked, and its event handler (and its invoked methods)
 * */
public class EventTraceAnalyzer {
	
	public final List<EventEntity> list;
	
	public EventTraceAnalyzer(UIActionTracer tracer) {
		Utils.checkNotNull(tracer);
		this.list = tracer.getAllUIEntities();
	}
	
	public EventTraceAnalyzer(List<EventEntity> list) {
		Utils.checkNotNull(list);
		this.list = list;
	}

	public void analyzeEventHandlers() {
		System.out.println("Size: " + this.list.size());
		
		boolean wellForm = checkTraceWellFormness();
		System.out.println("Well formness: " + wellForm);
		System.out.println("Has UI action? " + this.hasUIAction());
		
		if(this.hasUIAction()) {
			Map<UIActionEntity, List<Map<UIActionHandler, List<UIMethodEntity>>>> retMap = this.createMapForUIActionAndHandlers();
			System.out.println();
		    int i = 0;
		    for(UIActionEntity h : retMap.keySet()) {
			    System.out.println(i++);
			    System.out.println(h);
			    System.out.println("    " + retMap.get(h));
			    System.out.println();
		    }
		} else {
		    Map<UIActionHandler, List<UIMethodEntity>> retMap = this.createMapForHandlerAndCallees();
		    System.out.println();
		    int i = 0;
		    for(UIActionHandler h : retMap.keySet()) {
			    System.out.println(i++);
			    System.out.println(h);
			    System.out.println("    " + retMap.get(h));
			    System.out.println();
		    }
		}
	}
	
	public Map<UIActionEntity, Collection<UIActionHandler>> createActionToHandlerMap() {
		Map<UIActionEntity, Collection<UIActionHandler>> map = new LinkedHashMap<UIActionEntity, Collection<UIActionHandler>>();
		int index = 0;
		for(EventEntity uie : this.list) {
			if(uie instanceof UIActionEntity) {
				//e.g.,  action1, handler1, handler2, handler3  action2 action3
				// it returns handler1 for actions, returns null for action2
			   UIActionHandler handler = this.findTheImmediateInvokedHandler(index + 1, this.list);
			   if(handler != null) {
				   if(!map.containsKey(uie)) {
					   map.put((UIActionEntity)uie, new LinkedHashSet<UIActionHandler>());
				   }
				   map.get(uie).add(handler);
			   }
			}
			index++;
		}
		return map;
	}
	
	public Map<UIActionHandler, Collection<UIActionEntity>> createHandlerToActionMap() {
		Map<UIActionHandler, Collection<UIActionEntity>> map = new LinkedHashMap<UIActionHandler, Collection<UIActionEntity>>();
		int index = 0;
		for(EventEntity uie : this.list) {
			if(uie instanceof UIActionEntity) {
			   //find the next UIActionHandler
				//e.g.,  action1, handler1, handler2, handler3  action2 action3
				// it returns handler1 for actions, returns null for action2
			   UIActionHandler handler = this.findTheImmediateInvokedHandler(index + 1, this.list);
			   if(handler != null) {
				   if(!map.containsKey(handler)) {
					   map.put(handler, new LinkedHashSet<UIActionEntity>());
				   }
				   map.get(handler).add((UIActionEntity)uie); //this is different than the previous one
			   }
			}
			index++;
		}
		return map;
	}
	
	/**
	 * Used for users' recording and replay (if there is no action info)
	 *     - produce a map from handlers --> (a list of called method at the first level)
	 * Used for tools' recording and replay
	 *     -  action -> (a map from handlers --> (a list of called method))
	 * */
	public Map<UIActionHandler, List<UIMethodEntity>> createMapForHandlerAndCallees() {
		Utils.checkTrue(!this.hasUIAction());
		return createMapForHandlerAndCallees(this.list);
	}
	
	private Map<UIActionHandler, List<UIMethodEntity>> createMapForHandlerAndCallees(List<EventEntity> list) {
		Utils.checkNotNull(list);
		if(list.isEmpty()) {
			return null;
		}
		Map<UIActionHandler, List<UIMethodEntity>> map = new LinkedHashMap<UIActionHandler, List<UIMethodEntity>>();
		for(int index = 0; index < list.size(); index++) {
			EventEntity event = list.get(index);
			Utils.checkTrue(!(event instanceof UIActionEntity));
			if(event instanceof UIActionHandler) {
				UIActionHandler handler = (UIActionHandler)event;
				if(handler.isEntry) {
					int exitHandlerIndex = this.findFirstMatchedExitHandler(list, index + 1, handler.methodDesc, handler.thizObjHash);
					if(exitHandlerIndex != -1) {
						List<UIMethodEntity> entityList = this.createMethodEntityList(list.subList(index + 1, exitHandlerIndex + 1));
						map.put(handler, entityList); //XXX FIXME could over lapped
						index = exitHandlerIndex + 1; //reset the index here
					}
				}
			}
		}
		
		return map;
	}
	
	private List<UIMethodEntity> createMethodEntityList(List<EventEntity> list) {
		List<UIMethodEntity> ret = new LinkedList<UIMethodEntity>();
		for(int index = 0; index < list.size(); index++) {
			EventEntity e = list.get(index);
			if(e instanceof UIMethodEntity) {
				UIMethodEntity me = (UIMethodEntity)e;
				int nextIndex = this.findFirstMatchedMethod(list, index + 1, me.methodDesc, me.thizObjHash);
				if(nextIndex != -1) {
					ret.add(me);
					index = nextIndex + 1;
				}
			}
		}
		return ret;
	}
	
	public Map<UIActionEntity, List<Map<UIActionHandler, List<UIMethodEntity>>>> createMapForUIActionAndHandlers() {
		Utils.checkTrue(this.hasUIAction());
		Map<UIActionEntity, List<Map<UIActionHandler, List<UIMethodEntity>>>> map = new LinkedHashMap<UIActionEntity, List<Map<UIActionHandler, List<UIMethodEntity>>>>();
		
		for(int index = 0; index < this.list.size(); index++) {
			EventEntity e = this.list.get(index);
			if(!(e instanceof UIActionEntity)) {
				continue;
			}
			UIActionEntity action = (UIActionEntity)e;
			int nextActionIndex = this.findFirstActionEntity(this.list, index+1);
			List<EventEntity> subList = nextActionIndex == -1 ?
					this.list.subList(index + 1, this.list.size()) :
					this.list.subList(index + 1, nextActionIndex);
		    Map<UIActionHandler, List<UIMethodEntity>> handlerEntityMap = createMapForHandlerAndCallees(subList);
		    if(handlerEntityMap == null) {
		    	continue;
		    }
		    if(!map.containsKey(action)) {
		    	map.put(action, new LinkedList<Map<UIActionHandler, List<UIMethodEntity>>>());
		    }
		    map.get(action).add(handlerEntityMap);
		}
		
		return map;
	}
	
	
	
	private int findFirstActionEntity(List<EventEntity> list, int startIndex) {
		for(int index = startIndex; index < list.size(); index++) {
			if(list.get(index) instanceof UIActionEntity) {
				return index;
			}
		}
		return -1;
	}
	
	private int findFirstMatchedExitHandler(List<EventEntity> list, int startIndex, String methodDesc, int objHashcode) {
		Utils.checkTrue(list.size() > startIndex);
		for(int i = startIndex; i < list.size(); i ++) {
			EventEntity entity = list.get(i);
			if(entity instanceof UIActionHandler) {
				UIActionHandler handler = (UIActionHandler)entity;
				if(!handler.isEntry && handler.methodDesc.equals(methodDesc) && handler.thizObjHash == objHashcode) {
					return i;
				}
			}
		}
		return -1;
	}
	
	private int findFirstMatchedMethod(List<EventEntity> list, int startIndex, String methodDesc, int objHashcode) {
		Utils.checkTrue(list.size() > startIndex);
		for(int i = startIndex; i < list.size(); i ++) {
			EventEntity entity = list.get(i);
			if(entity instanceof UIMethodEntity) {
				UIMethodEntity m = (UIMethodEntity)entity;
				if(!m.isEntry && m.methodDesc.equals(methodDesc) && m.thizObjHash == objHashcode) {
					return i;
				}
			}
		}
		return -1;
	}
	
	public boolean hasUIAction() {
		for(EventEntity e : this.list) {
			if(e instanceof UIActionEntity) {
				return true;
			}
		}
		return false;
	}
	
	private boolean checkTraceWellFormness() {
		Map<String, Boolean> internalMap = new HashMap<String, Boolean>();
		for(EventEntity e : this.list) {
			if(e instanceof UIActionEntity) {
				continue;
			}
			if(e instanceof UIAbstractEntity) {
				UIAbstractEntity me = (UIAbstractEntity)e;
				String sig = me.methodDesc + me.thizObjHash;
				if(!internalMap.containsKey(sig)) {
					internalMap.put(sig, me.isEntry);
				} else {
					Boolean b = internalMap.get(sig);
					if(b != !me.isEntry) {
						System.out.println("   -- find: " + sig);
						return false; //not well-formness
					} else {
						internalMap.remove(sig);
					}
				}
				
			}  else {
				throw new Error("Unknown: " + e);
			}
		}
		return true;
	}
	
	/**
	 * The index is inclusive
	 * */
	private List<String> extractCallRelations(List<EventEntity> list, int startIndex, int endIndex) {
		Utils.checkTrue(endIndex > startIndex);
		List<String> retList = new LinkedList<String>();
		for(int index = startIndex; index <= endIndex; index++) {
			EventEntity e = list.get(index);
			if(e instanceof UIMethodEntity) {
				UIMethodEntity methodEntity = (UIMethodEntity)e;
				if(methodEntity.isEntry) {
					int matchedIndex = this.findFirstMatchedMethod(list, index, methodEntity.methodDesc, methodEntity.thizObjHash);
					Utils.checkTrue(matchedIndex > index && matchedIndex <= endIndex);
					retList.add(methodEntity.methodDesc);
					index = matchedIndex;
				}
			}
			if(e instanceof UIActionHandler) {
				UIActionHandler handler = (UIActionHandler)e;
				if(handler.isEntry) {
					int matchedIndex = this.findFirstMatchedExitHandler(list, index, handler.methodDesc, handler.thizObjHash);
					Utils.checkTrue(matchedIndex > index && matchedIndex <= endIndex);
					retList.add(handler.methodDesc);
					index = matchedIndex;
				}
			}
		}
		
		return retList;
	}
	
	private UIActionHandler findTheImmediateInvokedHandler(int from, List<EventEntity> list) {
		UIActionHandler handler = null;
		for (int i = from; i < list.size(); i++) {
			if (list.get(i) instanceof UIActionEntity) {
				break; //no more
			}
			if (list.get(i) instanceof UIActionHandler) {
				handler = (UIActionHandler) list.get(i);
				break; //find one
			}
		}
		return handler;
	}
}